package com.wsc.crawler;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Locale;

import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpServerConnection;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import com.wsc.crawler.init.*;


public class HeartBeatServer {

	private static Logger log = Logger.getLogger(HeartBeatServer.class
			.getName());
	private CrawlerConfig config;

	
	
	public HeartBeatServer(CrawlerConfig config){
		this.config = config;
		
	}
	
	
	public void start() throws IOException {

		// Get heartbeat Listen port from config.
		
		int heartBeatserver_port = config.getHeartBeat_Host().getPort();
		
		
		// Create a Listner socket on given Port from config.
		
		Thread t = new RequestListenerThread(heartBeatserver_port,
				CrawlerOperations.P_CONTEXT_PATH);

		// should be set false.
		t.setDaemon(false);
		
//		Start Server
		t.start();
	}

	// Request Handler

	static class HandleRequest implements HttpRequestHandler {

		public HandleRequest(final String docRoot) {
			super();
		}

		// this is overrided method from HttpRequestHandler Interface.

		public void handle(final HttpRequest request,
				final HttpResponse response, final HttpContext context)
				throws HttpException, IOException {

			// get method name from request.
			String method = request.getRequestLine().getMethod()
					.toUpperCase(Locale.ENGLISH);

			if (!method.equals("GET") && !method.equals("HEAD")
					&& !method.equals("POST")) {

				// if request method is not one of the above, then throw
				// Exception.

				throw new MethodNotSupportedException(method
						+ " method not supported");

			}

			// If Client post any data in its body part then this block will
			// execute.
			if (request instanceof HttpEntityEnclosingRequest) {

				// get entity from request.
				HttpEntity entity = ((HttpEntityEnclosingRequest) request)
						.getEntity();

				// Convert it into byteArray.
				String xml = EntityUtils.toString(entity);

				System.out.println("Incoming entity content (bytes): "
						+ xml.length());
	
				// writing xml to a file
	     	/*		
				File f=new File("postedURLS.xml");
				FileOutputStream st=new FileOutputStream(f);
				DataOutputStream d=new DataOutputStream(st);
				d.writeBytes(xml);
				d.close();
				st.close();
			*/
			}

			// Get the index of the character in requested path in HttpRequest.
			int index = request.getRequestLine().getUri().indexOf('?');

			// If index is greater than 0 means that requested path has atleat a
			// key-value pair.
			if (index > 0) {

				// Get the Query part of the requested path in HttpRequest.
				String querypart = request.getRequestLine().getUri()
						.substring(index + 1);

				// Construct a KEY-VALUE pair List from querypart string.
				// default delimator is &.

				List<NameValuePair> params = URLEncodedUtils.parse(querypart,
						Charset.defaultCharset());

				// check the list size.
				if (!params.isEmpty()) {

					// read key from NameValuePair object in List.
					String name = params.get(0).getName();

					// read value from NameValuePair object in List.
					String value = params.get(0).getValue();

				
					// If parameter is opearation

					if (CrawlerOperations.OPERATION.equals(name)) {

						// If value is heartbeat
						
						if (CrawlerOperations.O_CRAWLER_HEARTBEAT.equals(value)) {

							// set response status 200 OK
							
							response.setStatusCode(HttpStatus.SC_OK);
						
							// Construct a String Entity, with proper message.
							
							StringEntity entity = new StringEntity("amfine");
						
							// And set it in Response.
							
							response.setEntity(entity);
							
						} else {
							
							// IMPLEMENT OTHER OPERATION  REQUESTS VALUES HERE.
							
							
							log.debug("Opeartion value (" + value
									+ ") not yet Implemented");
							
						}

					} else {

						log.warn("Unsupported operation (" + name
								+ ") found in request.");

						// If operation parameter is not equals to
						// CrawlerOperations.OPERATION then send 404 notfound
						// message.

						response.setStatusCode(HttpStatus.SC_NOT_FOUND);

						// Set Entity message notfound in HTML.
						StringEntity entity = new StringEntity(
								"<html><body><h1>File"
										+ " not found</h1></body></html>",
								ContentType.create("text/html", "UTF-8"));

						// Set the Entity in Response meassage.
						response.setEntity(entity); // edit : not mandatory.

					}

				}

			}
 
		}

	}

	// Request Listner

	static class RequestListenerThread extends Thread {

		private final ServerSocket serversocket;
		private final HttpParams params;
		private final HttpService httpService;

		public RequestListenerThread(int port, final String docroot)
				throws IOException {

			// create a Listner socket with given port.
			this.serversocket = new ServerSocket(port);

			// set some basic HTTP parameters.
			this.params = new SyncBasicHttpParams();
			this.params
					.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
					.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE,
							8 * 1024)
					.setBooleanParameter(
							CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
					.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
					.setParameter(CoreProtocolPNames.ORIGIN_SERVER,
							"HttpComponents/1.1");

			
			// Set up the HTTP protocol processor
			HttpProcessor httpproc = new ImmutableHttpProcessor(
					new HttpResponseInterceptor[] { new ResponseDate(),
							new ResponseServer(), new ResponseContent(),
							new ResponseConnControl() });

			
			// Set up request handlers
			
			HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
			
			// Register the a custom handler class (HandleRequest).
			
			reqistry.register("*", new HandleRequest(docroot));

			// Set up the HTTP service
			
			this.httpService = new HttpService(httpproc,
					new DefaultConnectionReuseStrategy(),
					new DefaultHttpResponseFactory(), reqistry, this.params);
		}

		// Thread listens for clients.
		@Override
		public void run() {
			log.info("Listening on port "+ this.serversocket.getLocalPort());
			
			
			while (!Thread.interrupted()) {
				try {
					
					// Accept Client Connections here.
					
					Socket socket = this.serversocket.accept();
					
					// creating defaultHttpConnection here.
					DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
					System.out.println("Incoming connection from "
							+ socket.getInetAddress());
					
					// Bind the client with defaultHttpConnection object and set some params supplied.
					conn.bind(socket, this.params);

					// Start new thread for each client.
					
					Thread t = new WorkerThread(this.httpService, conn);
					t.setDaemon(true);
					
					t.start();
					
				} catch (InterruptedIOException ex) {
					
					// if any Exception occure then break the loop
					
					break;
				} catch (IOException e) {
					
					// if any Exception occure then break the loop
					System.err
							.println("I/O error initialising connection thread: "
									+ e.getMessage());
					break;
				}
			}
		}
	}

	// Worker Thread

	static class WorkerThread extends Thread {

		private final HttpService httpservice;
		private final HttpServerConnection conn;

		public WorkerThread(final HttpService httpservice,
				final HttpServerConnection conn) {
			super();
			this.httpservice = httpservice;
			this.conn = conn;
		}

		
		@Override
		public void run() {
			
			System.out.println("New connection thread");

			// create a basic HTTPContext with default options.
			
			HttpContext context = new BasicHttpContext(null);
			
			try {
				
				// untill the connection is OPEN, serve the requests made by client.
				
				while (!Thread.interrupted() && this.conn.isOpen()) {
					
					this.httpservice.handleRequest(this.conn, context);
					
				}
			} catch (ConnectionClosedException ex) {
			
				log.warn("Client closed connection", ex);

			} catch (IOException ex) {
				
				log.warn("I/O error ", ex);
				
			} catch (HttpException ex) {
				log.warn("Unrecoverable HTTP protocol violation: ", ex);
				
			} finally {
				try {
					
					// finally shutdown(close) the connection.
					this.conn.shutdown();
					
				} catch (IOException ignore) {
				}
			}
		}

	}

}